<script lang="ts" module>
    import { getContext, setContext } from "svelte";
    import type { FormContextOptions, FormProps } from "./Form";
    import { useClassList } from "../utils/useClassList";
    import type { ValidateError } from "async-validator";

    export const FormContextKey = Symbol("FormContextKey");
    export const useFormContext = () => getContext<FormContextOptions>(FormContextKey);
</script>

<script lang="ts">
    if (useFormContext()) {
        console.warn("Form can not be nested");
    }
    const {
        labelWidth,
        colon = true,
        hideRequiredMark = false,
        model,
        inline,
        rules,
        margin = 24,
        errorTransfer = false,
        errorAlign = "right",
        onchange,
        onsubmit,
        onfinish,
        onfinishfailed,
        ...props
    }: FormProps = $props();
    // 使用state封装，防止报ownership_invalid_mutation warning
    const modelState = $state(model);
    const originData = new Map<string, any>();
    const nameSet = new Set<string | (string | number)[]>();

    const classList = $derived(
        useClassList(props, "cm-form", {
            "cm-form-inline": inline,
        })
    );

    const controllers = $state<Map<string, Record<string, Function>>>(new Map());

    const setCheckValid = (name: string | (string | number)[], valid: (v: any, trigger: string) => Promise<ValidateError[] | null>) => {
        const nameKey = Array.isArray(name) ? name.join("_") : name;
        if (!controllers.has(nameKey)) {
            controllers.set(nameKey, {});
        }
        const controller = controllers.get(nameKey);
        controller!["valid"] = valid;
        // 缓存初始化值
        originData.set(nameKey, getValue(name));
        nameSet.add(name);
    };

    const setClearValid = (name: string | (string | number)[], clearValid: () => void) => {
        name = Array.isArray(name) ? name.join("_") : name;
        if (!controllers.has(name)) {
            controllers.set(name, {});
        }
        const controller = controllers.get(name);
        controller!["clearValid"] = clearValid;
    };

    const removeFieldValid = (name: string | (string | number)[]) => {
        nameSet.delete(name);
        name = Array.isArray(name) ? name.join("_") : name;
        if (controllers.has(name)) {
            controllers.delete(name);
        }
        // 清除缓存值
        originData.delete(name);
    };

    const onFieldChange = (name: string | (string | number)[], trigger: string) => {
        const val = getValue(name);
        onchange?.(name, $state.snapshot(val));
        const nameKey = Array.isArray(name) ? name.join("_") : name;

        const controller = controllers.get(nameKey);
        if (controller && controller.valid) {
            controller.valid(val, trigger);
        }
    };

    // 获取值
    const getValue = (name: string | (string | number)[], target = model) => {
        if (!target) {
            return undefined;
        }
        if (Array.isArray(name)) {
            return name.reduce((prev, cur) => {
                return prev[cur];
            }, target);
        } else {
            return target[name];
        }
    };

    // 设置值
    const setValue = (name: string | (string | number)[], value: any) => {
        if (!modelState) {
            return;
        }
        if (Array.isArray(name)) {
            const target = name.slice(0, -1);
            const key = name.slice(-1)[0];
            const targetValue = getValue(target, modelState);
            if (targetValue) {
                targetValue[key] = value;
            }
        } else {
            modelState[name] = value;
        }
    };

    const ctx: FormContextOptions = {
        labelWidth: () => labelWidth,
        colon,
        hideRequiredMark,
        inline: inline,
        model,
        rules: () => rules,
        errorTransfer: () => errorTransfer,
        errorAlign: () => errorAlign,
        setCheckValid,
        setClearValid,
        removeFieldValid,
        onFieldChange,
    };

    const onSubmit = async (e: Event) => {
        e.preventDefault();
        validate().then(() => {
            onsubmit?.(e);
        });
        return false;
    };

    setContext(FormContextKey, ctx);

    // 表单校验
    export const validate = async () => {
        const names = nameSet.keys();
        return validateFields(Array.from(names))
            .then(() => {
                onfinish?.();
                return null;
            })
            .catch((errors) => {
                onfinishfailed?.(errors);
                throw errors;
            });
    };

    // 指定字段进行校验
    export const validateFields = async (nameList: (string | (string | number)[])[]) => {
        const names = nameList;
        const promises = [];
        for (const name of names) {
            const val = getValue(name);
            const nameKey = Array.isArray(name) ? name.join("_") : name;
            if (controllers.has(nameKey)) {
                const controller = controllers.get(nameKey);
                promises.push(controller?.valid(val));
            }
        }
        const rets = await Promise.all(promises);
        const errors = rets.filter((v) => v).flat();
        if (errors.length > 0) {
            return Promise.reject(errors);
        } else {
            return null;
        }
    };

    // 清除指定字段的校验
    export const clearValidate = (nameList?: (string | (string | number)[])[]) => {
        const names = nameList ?? nameSet.keys();
        for (const name of names) {
            const nameKey = Array.isArray(name) ? name.join("_") : name;
            if (controllers.has(nameKey)) {
                const controller = controllers.get(nameKey);
                controller?.clearValid();
            }
        }
    };

    // 重置表单值并清除校验
    export const resetFields = (nameList?: (string | (string | number)[])[], data?: any) => {
        const names = nameList ?? nameSet.keys();
        for (const name of names) {
            const nameKey = Array.isArray(name) ? name.join("_") : name;
            const val = data ? getValue(name, data) : originData.get(nameKey);
            setValue(name, val);
        }
        clearValidate(nameList);
    };
</script>

<form class={classList} onsubmit={onSubmit} {...props} autocomplete="off" style:--cui-form-margin={`${margin}px`}>
    <!-- svelte-ignore a11y_consider_explicit_label -->
    <button type="submit" style="display: none"></button>
    {@render props.children?.()}
</form>
